package main

import (
	"cap_client/app"
	"context"
	"encoding/json"
	"fmt"
	"github.com/gorpher/gowin32"
	"github.com/gorpher/gowin32/wrappers"
	"github.com/sirupsen/logrus"
	"golang.org/x/sync/errgroup"
	"lib/log"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync/atomic"
)

var (
	sessionID uint32
	g         errgroup.Group
)

func main() {
	logFilePath := ""
	if dir, err := os.Getwd(); err == nil {
		logFilePath = dir + "./"
	}
	if err := os.MkdirAll(logFilePath, 0755); err != nil {
		fmt.Println(err.Error())
	}
	log.InitLog(filepath.Join(logFilePath, "logs", fmt.Sprintf("%d.log", sessionID)), LogLv)
	err := wrappers.ProcessIdToSessionId(wrappers.GetCurrentProcessId(), &sessionID)
	if err != nil {
		logrus.Error(err, "获取sessionID失败")
	}
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	ctx = app.WithContextFunc(ctx, func() {
		logrus.Error(err, "received signal, terminating process")
		cancel()
	})
	channel := NewVisualChannel()
	g.Go(func() error {
		err = channel.Serve(ctx)
		if err != nil {
			logrus.Error(err, "虚拟通道出错")
		}
		return err
	})
	g.Go(func() error {
		for {
			running := channel.running.Load()
			if running.(bool) {
				logrus.Debug("打开虚拟通道成功，发送会话win id")
				wToFrontend <- ChannelMsg{
					Code: ChannelMsgCodeForSayHi,
					Data: fmt.Sprintf("%d", sessionID),
				}
				return nil
			}
		}
	})

	err = g.Wait()
	if err != nil {
		logrus.Error(err, "launcher 退出")
	}
}

/////////// wts //////////////

type AppParams struct {
	Username string `json:"username"`
	Password string `json:"password"`
	Domain   string `json:"domain"`
	RunAs    bool   `json:"runAs"`

	HostSessionId int64    `json:"hostSessionId,string"`
	AppId         int64    `json:"appId,string"`
	ExePath       string   `json:"exePath"`
	WorkDir       string   `json:"workDir"`
	Params        []string `json:"params"`
}

type OpenAppSuccess struct {
	AppId         int64 `json:"appId,string"`
	WinSessionId  int   `json:"winSessionId"`
	HostSessionId int64 `json:"hostSessionId,string"`
}

type ChannelMsg struct {
	Code int    `json:"code"`
	Data string `json:"data"`
}

var wToFrontend = make(chan ChannelMsg, 20)

type VisualChannel struct {
	cancelFunc func()
	running    atomic.Value
}

func NewVisualChannel() *VisualChannel {
	c := &VisualChannel{
		running: atomic.Value{},
	}
	c.running.Store(false)
	return c
}

func (v *VisualChannel) Serve(c context.Context) (err error) {
	running := v.running.Load()
	if running.(bool) {
		return nil
	}
	v.running.Store(true)
	defer v.running.Store(false)
	logrus.Debug("准备启动系统通道监听服务")
	// hcy: 为什么长度为2？
	w := make(chan []byte, 2)
	r := make(chan []byte, 2)
	ctx, cancelFunc := context.WithCancel(c)
	defer close(w)
	defer close(r)
	v.cancelFunc = cancelFunc
	defer cancelFunc()
	var g2 errgroup.Group
	// 启动 wts 服务
	wtsServer := gowin32.OpenWTSServer("")
	// 为特定会话创建虚拟通道，并监听交互数据
	g2.Go(func() error {
		defer cancelFunc()
		logrus.Debug("Init Virtual Channel :", " ,sessionId :", sessionID)
		// 这里是一个阻塞式操作
		err = wtsServer.OpenWTSVirtualChannelWithSessionID(ctx, sessionID, ChannelName, w, r)
		if err != nil {
			logrus.Error("监听虚拟通道失败", err)
			return err
		}
		return nil
	})
	// 挂盘
	g2.Go(func() error {
		var userName string
		userName, err = wtsServer.QuerySessionUserName(uint(sessionID))
		if err != nil {
			logrus.Error("获取当前会话信息失败：", err)
			return err
		}
		cmd := exec.Command("subst", "Y:", fmt.Sprintf("E:\\user\\%s", userName))
		return cmd.Start()
	})

	//读取虚拟通道中的数据
	g2.Go(func() error {
		defer cancelFunc()
		for {
			select {
			case <-ctx.Done():
				return nil
			case buf := <-r:
				logrus.Debug("开始读取数据：", string(buf))
				if buf == nil {
					continue
					//return nil
				}
				var body ChannelMsg
				err = json.Unmarshal(buf, &body)
				if err != nil {
					logrus.Errorf("无效的请求参数：%s", string(buf))
					continue
				}
				logrus.Debug("从虚拟通道中读取到数据：", body)
				switch body.Code {
				case ChannelMsgCodeForOpenApp:
					var appParams AppParams
					if err = json.Unmarshal([]byte(body.Data), &appParams); err != nil {
						logrus.Error(err)
					} else {
						v.startApp(appParams)
					}
				default:
					logrus.Error("从虚拟通道中读取到了未知的消息：", body)
				}
			}
		}
	})
	// 将数据通过虚拟通道发送给前端
	g2.Go(func() error {
		defer cancelFunc()
		for {
			select {
			case <-ctx.Done():
				return nil
			case data := <-wToFrontend:
				var body []byte
				body, err = json.Marshal(data)
				if err != nil {
					logrus.Error(err)
					continue
				}
				logrus.Debug("将数据发送到虚拟通道中：", string(body))
				w <- body
			}
		}
	})
	return g2.Wait()
}

func (v *VisualChannel) startApp(input AppParams) {
	if !fileIsExist(input.ExePath) {
		// 向虚拟通道中写入消息，提醒执行文件不存在
		wToFrontend <- ChannelMsg{
			Code: ChannelMsgCodeForAppNotExist,
			Data: fmt.Sprintf("打开应用所需要的执行文件【%s】不存在", input.ExePath),
		}
	}
	err := startAPP(input)
	if err != nil {
		// 向虚拟通道中写入消息，提醒启动应用程序失败
		wToFrontend <- ChannelMsg{
			Code: ChannelMsgCodeForOpenAppErr,
			Data: fmt.Sprintf("启动应用时发生错误【%s】", err.Error()),
		}
	} else {
		var bs []byte
		bs, err = json.Marshal(OpenAppSuccess{
			AppId:         input.AppId,
			WinSessionId:  int(sessionID),
			HostSessionId: input.HostSessionId,
		})
		if err != nil {
			logrus.Error(err)
			return
		}
		wToFrontend <- ChannelMsg{
			Code: ChannelMsgCodeForOpenAppSuccess,
			Data: string(bs),
		}
	}
}

func (v *VisualChannel) Close() {

}

func fileIsExist(str string) bool {
	_, err := os.Stat(str)
	if err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

// StartAPP 启动应用
func startAPP(argus AppParams) error {
	if strings.ToLower(argus.Username) != "" && argus.Password != "" && argus.RunAs {
		// 调用自己提权启动应用
		currentPath, err := os.Executable()
		if err != nil {
			return err
		}
		cmd := exec.Command(currentPath, "-in")
		values := url.Values{}
		values.Set("username", argus.Username)
		values.Set("password", argus.Password)
		values.Set("domain", argus.Domain)
		values.Set("exePath", argus.ExePath)
		values.Set("workDir", argus.WorkDir)
		values.Set("params", strings.Join(argus.Params, " "))
		stdin, err := cmd.StdinPipe()
		if err != nil {
			return err
		}
		err = cmd.Start()
		if err != nil {
			return err
		}
		_, err = stdin.Write([]byte(values.Encode()))
		if err != nil {
			return err
		}
		_, err = stdin.Write([]byte("\n"))
		if err != nil {
			return err
		}
		return nil
	}
	cmd := exec.Command(argus.ExePath, argus.Params...) // nolint
	cmd.Dir = argus.WorkDir
	return cmd.Start()
}
